package com.rizhov.graphmodels;

import java.util.Arrays;

import com.rizhov.enumerations.GraphModelType;
import com.rizhov.enumerations.GraphOrientation;

public class MatrixGraphModel implements GraphModel {

    // can be DIRECTED_GRAPH or UNDIRECTED_GRAPH
    private GraphOrientation dirrectionType;

    // vertex count and edge count
    private int edgeCount;
    private int vertexCount;

    // storages of data
    private Matrix edgeDataMatrix = new Matrix();
    private Object[] vertexDataArr = new Object[0];
    private Matrix weightMatrix = new Matrix();

    public MatrixGraphModel() {
	dirrectionType = GraphOrientation.ORIENTED_GRAPH;
    }

    public MatrixGraphModel(GraphOrientation graphDirrection) {
	dirrectionType = graphDirrection;
    }

    @Override
    public boolean addEdge(int startIndex, int endIndex, Object edgeWeight) {
	if (startIndex == endIndex) {
	    return false;
	}
	if (startIndex >= 0 && startIndex < vertexCount && endIndex >= 0 && endIndex < vertexCount
		&& vertexCount != 0) {
	    if (!isEdgeExist(startIndex, endIndex)) {
		weightMatrix.setCell(startIndex, endIndex, edgeWeight);
		edgeCount++;
	    }
	    if (dirrectionType == GraphOrientation.UNORIENTED_GRAPH) {
		if (!isEdgeExist(endIndex, startIndex)) {
		    weightMatrix.setCell(endIndex, startIndex, edgeWeight);
		    edgeCount++;
		}
	    }
	    return true;
	} else {
	    return false;
	}
    }

    @Override
    public boolean addVertex(Object vertexData) {
	if (vertexCount > 20) {
	    return false;
	}
	vertexDataArr = Arrays.copyOf(vertexDataArr, vertexDataArr.length + 1);
	vertexDataArr[vertexDataArr.length - 1] = vertexData;

	weightMatrix.addColumn(weightMatrix.getColumnCount());
	edgeDataMatrix.addColumn(edgeDataMatrix.getColumnCount());

	if (vertexCount != 0) {
	    weightMatrix.addRow(weightMatrix.getRowCount());
	    edgeDataMatrix.addRow(edgeDataMatrix.getRowCount());
	}
	vertexCount++;
	return true;
    }

    @Override
    public Object clone() {
	MatrixGraphModel clone = new MatrixGraphModel();
	clone.dirrectionType = this.dirrectionType;
	clone.edgeCount = this.edgeCount;
	clone.edgeDataMatrix = this.edgeDataMatrix.clone();
	clone.vertexCount = this.vertexCount;
	clone.vertexDataArr = Arrays.copyOf(this.vertexDataArr, this.vertexDataArr.length);
	clone.weightMatrix = this.weightMatrix.clone();
	return clone;
    }

    @Override
    public int getEdgeCount() {

	return edgeCount;

    }

    @Override
    public Object getEdgeData(int startIndex, int endIndex) {
	return edgeDataMatrix.getCell(startIndex, endIndex);
    }

    @Override
    public Object getEdgeWeight(int startIndex, int endIndex) {
	return weightMatrix.getCell(startIndex, endIndex);
    }

    @Override
    public GraphOrientation getGraphDirectionType() {
	return dirrectionType;
    }

    @Override
    public GraphModelType getGraphModelType() {
	return GraphModelType.MATRIX_MODEL_TYPE;
    }

    @Override
    public double getGraphSaturationRatio() {
	if (edgeCount == 0) {
	    return 0;
	} else {
	    double dEdgeCount = edgeCount;
	    double dVertexCount = vertexCount;
	    return (dEdgeCount) / (dVertexCount * (dVertexCount - 1));
	}
    }

    @Override
    public String getStatisticsString() {
	StringBuilder sb = new StringBuilder();
	sb.append("Weight matrix is: \n");
	sb.append(weightMatrix.toString());
	sb.append("Edge data matrix is: \n");
	sb.append(edgeDataMatrix.toString());
	sb.append("Vertex data: " + Arrays.toString(vertexDataArr));
	return sb.toString();
    }

    @Override
    public int getVertexCount() {
	return vertexCount;
    }

    @Override
    public Object getVertexData(int index) {
	if (index >= 0 && index < vertexDataArr.length) {
	    return vertexDataArr[index];
	} else {
	    return null;
	}
    }

    @Override
    public int getVertexIndex(Object vertexData) {
	for (int i = 0; i < vertexDataArr.length; i++) {
	    if (vertexDataArr[i].equals(vertexData)) {
		return i;
	    }
	}
	return -1;
    }

    @Override
    public boolean isEdgeExist(int startIndex, int endIndex) {
	if (weightMatrix.isCellFull(startIndex, endIndex)) {
	    return true;
	} else {
	    return false;
	}
    }

    @Override
    public void printGraphIntoConsole() {
	System.out.println("*in weight matrix free cell means connection absence \n"
		+ " in edge data matrix free cell means data absence");
	System.out.println("Weight matrix is:");
	System.out.println(weightMatrix.toString());
	System.out.println("Edge data matrix is:");
	System.out.println(edgeDataMatrix.toString());
    }

    @Override
    public boolean removeEdge(int startIndex, int endIndex) {
	boolean successfulResult = false;
	if (weightMatrix.isCellFull(startIndex, endIndex)) {
	    weightMatrix.clearCell(startIndex, endIndex);
	    edgeDataMatrix.clearCell(startIndex, endIndex);
	    edgeCount--;
	    successfulResult = true;
	}
	if (dirrectionType == GraphOrientation.UNORIENTED_GRAPH) {
	    if (weightMatrix.isCellFull(endIndex, startIndex)) {
		weightMatrix.clearCell(endIndex, startIndex);
		edgeDataMatrix.clearCell(endIndex, startIndex);
		edgeCount--;
		successfulResult = true;
	    }
	}
	return successfulResult;
    }

    @Override
    public boolean removeVertex(Object vertexData) {
	for (int i = 0; i < vertexDataArr.length; i++) {
	    if (vertexDataArr[i].equals(vertexData)) {
		for (int j = 0; j < vertexCount; j++) {
		    if (weightMatrix.isCellFull(i, j)) {
			edgeCount--;
		    }
		    if (weightMatrix.isCellFull(j, i)) {
			edgeCount--;
		    }
		}
		weightMatrix.removeRow(i);
		edgeDataMatrix.removeRow(i);
		weightMatrix.removeColumn(i);
		edgeDataMatrix.removeColumn(i);

		vertexCount--;

		Object[] newVertexDataArr = new Object[vertexDataArr.length - 1];
		for (int j = 0; j < newVertexDataArr.length; j++) {
		    if (j >= i) {
			newVertexDataArr[j] = vertexDataArr[j + 1];
		    } else {
			newVertexDataArr[j] = vertexDataArr[j];
		    }
		}
		vertexDataArr = newVertexDataArr;
		return true;
	    }
	}
	return false;
    }

    @Override
    public boolean setEdgeData(int startIndex, int endIndex, Object edgeData) {
	if (startIndex >= 0 && startIndex < vertexCount && endIndex >= 0 && endIndex < vertexCount
		&& vertexCount != 0) {
	    edgeDataMatrix.setCell(startIndex, endIndex, edgeData);
	    if (dirrectionType == GraphOrientation.UNORIENTED_GRAPH) {
		edgeDataMatrix.setCell(endIndex, startIndex, edgeData);
	    }
	    return true;
	} else {
	    return false;
	}
    }

    @Override
    public boolean setEdgeWeight(int startIndex, int endIndex, Object edgeWeight) {
	if (startIndex >= 0 && startIndex < vertexCount && endIndex >= 0 && endIndex < vertexCount
		&& vertexCount != 0) {
	    weightMatrix.setCell(startIndex, endIndex, edgeWeight);
	    if (dirrectionType == GraphOrientation.UNORIENTED_GRAPH) {
		weightMatrix.setCell(endIndex, startIndex, edgeWeight);
	    }
	    return true;
	} else {
	    return false;
	}
    }

    @Override
    public void setGraphOrientation(GraphOrientation graphOrientation) {
	if (this.dirrectionType == GraphOrientation.ORIENTED_GRAPH
		&& graphOrientation == GraphOrientation.UNORIENTED_GRAPH) {
	    for (int i = 0; i < vertexCount; i++) {
		for (int j = 0; j < vertexCount; j++) {
		    if (weightMatrix.isCellFull(i, j) && weightMatrix.isCellFull(j, i)) {
			weightMatrix.setCell(j, i, weightMatrix.getCell(i, j));
			edgeDataMatrix.setCell(j, i, edgeDataMatrix.getCell(i, j));
		    }
		    if (weightMatrix.isCellFull(i, j)) {
			if (!weightMatrix.isCellFull(j, i)) {
			    edgeCount++;
			}
			weightMatrix.setCell(j, i, weightMatrix.getCell(i, j));
			edgeDataMatrix.setCell(j, i, edgeDataMatrix.getCell(i, j));
		    } else if (weightMatrix.isCellFull(j, i)) {
			if (!weightMatrix.isCellFull(i, j)) {
			    edgeCount++;
			}
			weightMatrix.setCell(i, j, weightMatrix.getCell(j, i));
			edgeDataMatrix.setCell(i, j, edgeDataMatrix.getCell(j, i));
		    }
		}
	    }
	}
	dirrectionType = graphOrientation;
    }

    @Override
    public boolean setVertexData(Object newVertexData, Object oldVertexData) {
	int vertexIndex = getVertexIndex(oldVertexData);
	if (vertexIndex != -1) {
	    vertexDataArr[vertexIndex] = newVertexData;
	    return true;
	} else {
	    return false;
	}
    }

}
